/**
 * 
 */
package ch.gje.playios.server;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.nio.charset.Charset;
import java.util.HashMap;

import ch.gje.playios.server.exception.BadHTTPRequestException;
import java.nio.ByteBuffer;

/**
 * A HTTP connection
 * 
 * @author jannis <jannis@gje.ch>
 */
public class HTTPConnection {

    /**
     * HTTP request method (pseudo header)
     */
    public static final String REQUEST_METHOD = "HTTP-Request-Method";
    /**
     * HTTP requested resource (pseudo header)
     */
    public static final String REQUEST_RESOURCE = "HTTP-Request-Resource";
    /**
     * HTTP standard (pseudo header)
     */
    public static final String REQUEST_STANDARD = "HTTP-Request-Standard";
    /**
     * The client socket
     */
    private Socket socket;
    /**
     * Input stream
     */
    private BufferedInputStream inputStream;
    /**
     * Output stream
     */
    private BufferedOutputStream outputStream;
    /**
     * The request as byte array
     */
    private byte[] request;
    /**
     * The request headers
     */
    private HashMap<String, String> headers;

    /**
     * Constructor
     *
     * @param socket
     *            the client socket
     */
    public HTTPConnection(Socket socket) {
        this.socket = socket;
        try {
            inputStream = new BufferedInputStream(socket.getInputStream());
            outputStream = new BufferedOutputStream(socket.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * sets the http request
     *
     * @param request
     *            the request
     * @throws BadHTTPRequestException
     */
    public void setRequest(byte[] request) throws BadHTTPRequestException {
        this.request = request;
        headers = new HashMap<String, String>();

        String[] lines = new String(request).split("\\r\\n");
        String[] queryParts = lines[0].split(" ");
        if (queryParts.length == 3) {
            headers.put(REQUEST_METHOD, queryParts[0]);
            headers.put(REQUEST_RESOURCE, queryParts[1]);
            headers.put(REQUEST_STANDARD, queryParts[2]);

            for (String line : lines) {
                String[] lineParts = line.split(": ");
                if (lineParts.length == 2) {
                    headers.put(lineParts[0], lineParts[1]);
                }
            }
        } else {
            throw new BadHTTPRequestException();
        }
    }

    /**
     * Reads from input to buffer
     *
     * @param buffer
     *            To be filled with input bytes
     * @return number of read bytes or -1 if no input
     * @throws IOException
     */
    public int read(byte[] buffer) throws IOException {
        if (!socket.isInputShutdown()) {
            try {
                return inputStream.read(buffer);
            } catch (SocketException e) {
                /* Socket closed unexpected */
                return -1;
            }
        } else {
            return -1;
        }
    }

    /**
     * Sends a string to the client if request method is not HEAD
     *
     * @param message
     *            the string
     * @throws IOException
     */
    public void send(String message) throws IOException {
        send(message, false);
    }

    /**
     * Sends bytes to the client if request method is not HEAD
     *
     * @param bytes
     *            the bytes
     * @throws IOException
     */
    public void send(byte[] bytes) throws IOException {
        send(bytes, false);
    }

    /**
     * Sends a string as response header part to client
     *
     * @param message
     *            the header string
     * @throws IOException
     */
    public void sendHeader(String message) throws IOException {
        send(message, true);
    }

    /**
     * Sends a string to the client over the output stream
     *
     * @param message
     *            the string
     * @param alsoHEAD
     *            if string should also be sent if request method is HEAD
     * @throws IOException
     */
    public void send(String message, boolean alsoHEAD) throws IOException {
        send(message.getBytes(Charset.forName("UTF-8")), alsoHEAD);
    }

    /**
     * Sends bytes to the client over the output stream
     *
     * @param bytes
     *            the bytes
     * @param alsoHEAD
     *            if string should also be sent if request method is HEAD
     * @throws IOException
     */
    public void send(byte[] bytes, boolean alsoHEAD) throws IOException {
        if (alsoHEAD || headers.containsKey(REQUEST_METHOD)
                && !headers.get(REQUEST_METHOD).equals("HEAD")) {
            if (!socket.isOutputShutdown()) {
                try {
                    outputStream.write(bytes);
                    outputStream.flush();
                } catch (SocketException e) {
                    /* Socket closed unexpected */
                }
            }
        }
    }

    /**
     * Sends a String as a WebSocket frame
     * @param message the String
     * @throws IOException
     */
    public void sendWebSocketFrame(String message) throws IOException {
        byte[] textBytes = message.getBytes(Charset.forName("UTF-8"));
        ByteBuffer send = ByteBuffer.allocate(textBytes.length + 2);
        send.put((byte) 0x00);
        send.put(textBytes);
        send.put((byte) 0xFF);
        send(send.array());
    }

    /**
     * Returns if the connection is closed
     *
     * @return true if the connection is closed
     */
    public boolean isClosed() {
        return socket.isClosed();
    }

    /**
     * Closes the connection
     *
     * @throws IOException
     */
    public void close() throws IOException {
        socket.close();
    }

    /**
     * @return the headers
     */
    public HashMap<String, String> getHeaders() {
        return headers;
    }

    /**
     * @return the request
     */
    public byte[] getRequest() {
        return request;
    }
}
